', 'foo-- ', 'foo--! ', '[if IE] ', 'foo- ', 'foo- ', ' > ', '---------- > ', '--\uFFFD>']); }); it('filter yav-single-quoted state transition test', function() { testutils.test_yav(filter.yavs, [ 'foo& <> '"` \t\n\x0B\f\r', '\f', '', '''', ' ''', '\t ''', '\n ''', '\f ''', '""', ' ""', '\t""', '\n""', '\f""', '``', ' ``', '\t``', '\n``', '\f``']); }); it('filter yav-double-quoted state transition test', function() { testutils.test_yav(filter.yavd, [ 'foo& <> \'"` \t\n\x0B\f\r', '\f', '', "''", " ''", "\t''", "\n''", "\f''", '""', ' ""', '\t ""', '\n ""', '\f ""', '``', ' ``', '\t``', '\n``', '\f``']); }); it('filter yav-unquoted state transition test', function() { testutils.test_yav(filter.yavu, [ 'foo&<>'"` ', '', '\uFFFD', "''", " ''", " ''", " ''", "''", '""', ' ""', ' ""', ' ""', '""', '``', ' ``', ' ``', ' ``', '``']); var s = "\x00=<>''onerror=alert(1)"; var o = filter.yavu(s); expect(o).to.eql("\uFFFD =<>''onerror =alert(1)"); }); it('filter yu state transition test', function() { testutils.test_yu(filter.yu); }); it('filter yuc state transition test', function() { testutils.test_yuc(filter.yuc); }); it('filter yubl state transition test', function() { testutils.test_yubl(filter.yubl, [ 'x-\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u0009\ \u000A\u000B\u000C\u000D\u000E\u000F\u0010\u0011\u0012\ \u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\ \u001C\u001D\u001E\u001F\u0020j\nav a\rscript\t &col \u0000on;' ]); }); it('filter yufull state transition test', function() { testutils.test_yufull(filter.yufull, [ null, // default null, // default null, // default null, // default null, // default 'http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]', null, // default null // default ]); }); it('filter yublf state transition test', function() { testutils.test_yufull(filter.yublf, [ null, // default null, // default null, // default null, // default null, // default 'http://[2001:0db8:85a3:0000:0000:8a2e:0370:7334]', null, // default null // default ]); testutils.test_yubl(filter.yublf, [ '%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20j%0Aav a%0Dscript%09 &col %00on;' ]); }); }); describe("private-xss-filters: css expression tests", function() { var testPatterns = [ undefined, null, '&', '1.1', '10%', '+10px', '-10px', '#fff', '\uD7FF', '\uD800', '\uDFFF', '\u1234567', '\u0000', ' ', '\r\n\t\f\x0B', '\\', '\\n\\r\\f\\0\\9\\a\\f', '-ide_nt', '"string"', "'string'", '- \ _ : ; ( ) " \' / , % # ! * @ . { } []', 'http://username:password@www.evil.com:8080/?k1=v1 &k2 =v2#hash', 'url(https://www.evil.com)', 'u\x00\x00rl(https://www.evil.com)', '\\u\\r\x00\x00\\l\x00\\((evil.com))', 'expression(body.scrollTop + 50 + px)', '(((()))) \\28 \\29' ]; it('filter yceu[uds] test', function() { var expectedResults = [ 'undefined', 'null', ';-x:\'&\';-v:', '1.1', '10%', '+10px', '-10px', '#fff', ';-x:\'\uD7FF\';-v:', ';-x:\'\uD800\';-v:', ';-x:\'\uDFFF\';-v:', ';-x:\'\u1234567\';-v:', ';-x:\'\uFFFD\';-v:', ';-x:\' \';-v:', ';-x:\'\\d \\a \\9 \\c \\b \';-v:', ';-x:\'\\5c \';-v:', ';-x:\'\\5c n\\5c r\\5c f\\5c 0\\5c 9\\5c a\\5c f\';-v:', '-ide_nt', ';-x:\'"string"\';-v:', ';-x:\'\\27 string\\27 \';-v:', ';-x:\'- _ : ; ( ) " \\27 / , % # ! * @ . \\7b \\7d \\5b \\5d \';-v:', ';-x:\'http://username:password@www.evil.com:8080/?k1=v1 &k2 =v2#hash\';-v:', ';-x:\'-x-url(https://www.evil.com)\';-v:', ';-x:\'u\ufffd\ufffdrl(https://www.evil.com)\';-v:', ';-x:\'\\5c u\\5c r\ufffd\ufffd\\5c l\ufffd\\5c ((evil.com))\';-v:', ';-x:\'expression(body.scrollTop + 50 + px)\';-v:', ';-x:\'(((()))) \\5c 28 \\5c 29\';-v:' ]; testutils.test_yce(filter.yceu, testPatterns, expectedResults); }); it('filter yced[uds] test', function() { var expectedResults = [ 'undefined', 'null', '&', '1.1', '10%', '+10px', '-10px', '#fff', '\uD7FF', '\uD800', '\uDFFF', '\u1234567', '\uFFFD', ' ', '\\d \\a \\9 \\c \\b ', '\\5c ', '\\5c n\\5c r\\5c f\\5c 0\\5c 9\\5c a\\5c f', '-ide_nt', '\\22 string\\22 ', "'string'", '- _ : ; ( ) \\22 \' / , % # ! * @ . \\7b \\7d \\5b \\5d ', 'http://username:password@www.evil.com:8080/?k1=v1 &k2 =v2#hash', '-x-url(https://www.evil.com)', 'u\ufffd\ufffdrl(https://www.evil.com)', '\\5c u\\5c r\ufffd\ufffd\\5c l\ufffd\\5c ((evil.com))', 'expression(body.scrollTop + 50 + px)', '(((()))) \\5c 28 \\5c 29' ]; testutils.test_yce(filter.yced, testPatterns, expectedResults); }); it('filter yces[uds] test', function() { var expectedResults = [ 'undefined', 'null', '&', '1.1', '10%', '+10px', '-10px', '#fff', '\uD7FF', '\uD800', '\uDFFF', '\u1234567', '\uFFFD', ' ', '\\d \\a \\9 \\c \\b ', '\\5c ', '\\5c n\\5c r\\5c f\\5c 0\\5c 9\\5c a\\5c f', '-ide_nt', '"string"', "\\27 string\\27 ", '- _ : ; ( ) " \\27 / , % # ! * @ . \\7b \\7d \\5b \\5d ', 'http://username:password@www.evil.com:8080/?k1=v1 &k2 =v2#hash', '-x-url(https://www.evil.com)', 'u\ufffd\ufffdrl(https://www.evil.com)', '\\5c u\\5c r\ufffd\ufffd\\5c l\ufffd\\5c ((evil.com))', 'expression(body.scrollTop + 50 + px)', '(((()))) \\5c 28 \\5c 29' ]; testutils.test_yce(filter.yces, testPatterns, expectedResults); }); }); describe("private-xss-filters: css url tests", function() { var testPatterns = [ undefined, null, '&', '1.1', '10%', '+10px', '-10px', '#fff', '\\a', '\uD7FF', '\u1234567', '\u0000', ' ', '\r\n\t\f\x0B', '\\', '\\n\\r\\f\\0\\9\\a\\f', '-ide_nt', '"string"', "'string'", '- \ _ : ; ( ) " \' / , % # ! * @ . { } [ ]', 'http://username:password@www.evil.com:8080/?k1=v1 &k2 =v2#hash', '\u0000\u0008\u000b\u007f\u000e-\u001f', ')))((('''"""" ', 'javascript:alert(1)', '(((()))) \\28 \\29' ]; it('filter yceuu[uds] attribute test', function() { var expectedResults = [ 'undefined', 'null', '&', '1.1', '10%25', '+10px', '-10px', '#fff', '%5Ca', '%ED%9F%BF', '%E1%88%B4567', '%EF%BF%BD', '%20', '%0D%0A%09%0C%0B', '%5C', '%5Cn%5Cr%5Cf%5C0%5C9%5Ca%5Cf', '-ide_nt', '%22string%22', "\\27 string\\27 ", '-%20%20_%20:%20;%20%28%20%29%20%22%20\\27 %20/%20,%20%25%20#%20!%20*%20@%20.%20%7B%20%7D%20%5B%20%5D', 'http://username:password@www.evil.com:8080/?k1=v1 &k2 =v2#hash', '%EF%BF%BD%08%0B%7F%0E-%1F', '%29%29%29%28%28%28\\27 \\27 \\27 %22%22%22%22', '##javascript:alert%281%29', '%28%28%28%28%29%29%29%29%20%5C28%20%5C29' ]; testutils.test_yce(filter.yceuu, testPatterns, expectedResults); }); it('filter yceud[uds] test', function() { var expectedResults = [ 'undefined', 'null', '&', '1.1', '10%25', '+10px', '-10px', '#fff', '%5Ca', '%ED%9F%BF', '%E1%88%B4567', '%EF%BF%BD', '%20', '%0D%0A%09%0C%0B', '%5C', '%5Cn%5Cr%5Cf%5C0%5C9%5Ca%5Cf', '-ide_nt', '%22string%22', "'string'", '-%20%20_%20:%20;%20(%20)%20%22%20\'%20/%20,%20%25%20#%20!%20*%20@%20.%20%7B%20%7D%20%5B%20%5D', 'http://username:password@www.evil.com:8080/?k1=v1 &k2 =v2#hash', '%EF%BF%BD%08%0B%7F%0E-%1F', ')))(((\'\'\'%22%22%22%22', '##javascript:alert(1)', '(((())))%20%5C28%20%5C29' ]; testutils.test_yce(filter.yceud, testPatterns, expectedResults); }); it('filter yceus[uds] test', function() { var expectedResults = [ 'undefined', 'null', '&', '1.1', '10%25', '+10px', '-10px', '#fff', '%5Ca', '%ED%9F%BF', '%E1%88%B4567', '%EF%BF%BD', '%20', '%0D%0A%09%0C%0B', '%5C', '%5Cn%5Cr%5Cf%5C0%5C9%5Ca%5Cf', '-ide_nt', '%22string%22', "\\27 string\\27 ", '-%20%20_%20:%20;%20(%20)%20%22%20\\27 %20/%20,%20%25%20#%20!%20*%20@%20.%20%7B%20%7D%20%5B%20%5D', 'http://username:password@www.evil.com:8080/?k1=v1 &k2 =v2#hash', '%EF%BF%BD%08%0B%7F%0E-%1F', ')))(((\\27 \\27 \\27 %22%22%22%22', '##javascript:alert(1)', '(((())))%20%5C28%20%5C29' ]; testutils.test_yce(filter.yceus, testPatterns, expectedResults); }); }); describe("private-xss-filters: utility tests", function() { it('htmlDecode d exists', function() { expect(filter.d).to.be.ok(); }); it('htmlDecode d test', function() { expect(filter.d(null)).to.equal('null'); expect(filter.d()).to.equal('undefined'); expect(filter.d('Á ')).to.equal('Á\uFFFD\uFFFD\u20AC\u201A\u201D\u0178\uFFFD\uFFFD'); }); it('frCoPt exists', function() { expect(filter.frCoPt).to.be.ok(); }); it('frCoPt test', function() { expect(filter.frCoPt(null)).to.equal(''); expect(filter.frCoPt()).to.equal(''); expect(filter.frCoPt(0)).to.equal('\uFFFD'); expect(filter.frCoPt(10)).to.equal('\n'); expect(filter.frCoPt(0x0B)).to.equal('\uFFFD'); expect(filter.frCoPt(0x10FFFF)).to.equal('\uFFFD'); }); }); }()); exports._getPrivFilters = function () { var LT = /])/g, SPECIAL_HTML_CHARS = /[&<>"'`]/g, SPECIAL_COMMENT_CHARS = /(?:\x00|^-*!?>|--!?>|--?!?$|\]>|\]$)/g; var SENSITIVE_HTML_ENTITIES = /&(?:#([xX][0-9A-Fa-f]+|\d+);?|(Tab|NewLine|colon|semi|lpar|rpar|apos|sol|comma|excl|ast|midast|ensp|emsp|thinsp);|(nbsp|amp|AMP|lt|LT|gt|GT|quot|QUOT);?)/g, SENSITIVE_NAMED_REF_MAP = {Tab: '\t', NewLine: '\n', colon: ':', semi: ';', lpar: '(', rpar: ')', apos: '\'', sol: '/', comma: ',', excl: '!', ast: '*', midast: '*', ensp: '\u2002', emsp: '\u2003', thinsp: '\u2009', nbsp: '\xA0', amp: '&', lt: '<', gt: '>', quot: '"', QUOT: '"'}; var CSS_VALID_VALUE = /^(?:(?!-*expression)#?[-\w]+|[+-]?(?:\d+|\d*\.\d+)(?:r?em|ex|ch|cm|mm|in|px|pt|pc|%|vh|vw|vmin|vmax)?|!important|)$/i, CSS_DOUBLE_QUOTED_CHARS = /[\x00-\x1F\x7F\[\]{}\\"]/g, CSS_SINGLE_QUOTED_CHARS = /[\x00-\x1F\x7F\[\]{}\\']/g, CSS_BLACKLIST = /url[\(\u207D\u208D]+/g, CSS_UNQUOTED_URL = /['\(\)]/g; var URL_IPV6 = /\/\/%5[Bb]([A-Fa-f0-9:]+)%5[Dd]/; var URI_BLACKLIST_PROTOCOLS = {'javascript':1, 'data':1, 'vbscript':1, 'mhtml':1, 'x-schema':1}, URI_PROTOCOL_COLON = /(?::|[xX]0*3[aA];?|*58;?|:)/, URI_PROTOCOL_WHITESPACES = /(?:^[\x00-\x20]+|[\t\n\r\x00]+)/g, URI_PROTOCOL_NAMED_REF_MAP = {Tab: '\t', NewLine: '\n'}; var x, strReplace = function (s, regexp, callback) { return s === undefined ? 'undefined' : s === null ? 'null' : s.toString().replace(regexp, callback); }, fromCodePoint = String.fromCodePoint || function(codePoint) { if (arguments.length === 0) { return ''; } if (codePoint <= 0xFFFF) { return String.fromCharCode(codePoint); } codePoint -= 0x10000; return String.fromCharCode((codePoint >> 10) + 0xD800, (codePoint % 0x400) + 0xDC00); }; function getProtocol(str) { var s = str.split(URI_PROTOCOL_COLON, 2); return (s[0] && (s.length === 2 || str.length !== s[0].length)) ? s[0] : null; } function htmlDecode(s, namedRefMap, reNamedRef, skipReplacement) { namedRefMap = namedRefMap || SENSITIVE_NAMED_REF_MAP; reNamedRef = reNamedRef || SENSITIVE_HTML_ENTITIES; function regExpFunction(m, num, named, named1) { if (num) { num = Number(num[0] <= '9' ? num : '0' + num); return skipReplacement ? fromCodePoint(num) : num === 0x80 ? '\u20AC' : num === 0x82 ? '\u201A' : num === 0x83 ? '\u0192' : num === 0x84 ? '\u201E' : num === 0x85 ? '\u2026' : num === 0x86 ? '\u2020' : num === 0x87 ? '\u2021' : num === 0x88 ? '\u02C6' : num === 0x89 ? '\u2030' : num === 0x8A ? '\u0160' : num === 0x8B ? '\u2039' : num === 0x8C ? '\u0152' : num === 0x8E ? '\u017D' : num === 0x91 ? '\u2018' : num === 0x92 ? '\u2019' : num === 0x93 ? '\u201C' : num === 0x94 ? '\u201D' : num === 0x95 ? '\u2022' : num === 0x96 ? '\u2013' : num === 0x97 ? '\u2014' : num === 0x98 ? '\u02DC' : num === 0x99 ? '\u2122' : num === 0x9A ? '\u0161' : num === 0x9B ? '\u203A' : num === 0x9C ? '\u0153' : num === 0x9E ? '\u017E' : num === 0x9F ? '\u0178' : (num >= 0xD800 && num <= 0xDFFF) || num === 0x0D ? '\uFFFD' : x.frCoPt(num); } return namedRefMap[named || named1] || m; } return s === undefined ? 'undefined' : s === null ? 'null' : s.toString().replace(NULL, '\uFFFD').replace(reNamedRef, regExpFunction); } function cssEncode(chr) { return '\\' + chr.charCodeAt(0).toString(16).toLowerCase() + ' '; } function cssBlacklist(s) { return s.replace(CSS_BLACKLIST, function(m){ return '-x-' + m; }); } function cssUrl(s) { s = x.yufull(htmlDecode(s)); var protocol = getProtocol(s); return (protocol && URI_BLACKLIST_PROTOCOLS[protocol.toLowerCase()]) ? '##' + s : s; } return (x = { frCoPt: function(num) { return num === undefined || num === null ? '' : !isFinite(num = Number(num)) || num <= 0 || num > 0x10FFFF || (num >= 0x01 && num <= 0x08) || (num >= 0x0E && num <= 0x1F) || (num >= 0x7F && num <= 0x9F) || (num >= 0xFDD0 && num <= 0xFDEF) || num === 0x0B || (num & 0xFFFF) === 0xFFFF || (num & 0xFFFF) === 0xFFFE ? '\uFFFD' : fromCodePoint(num); }, d: htmlDecode, yup: function(s) { s = getProtocol(s.replace(NULL, '')); return s ? htmlDecode(s, URI_PROTOCOL_NAMED_REF_MAP, null, true).replace(URI_PROTOCOL_WHITESPACES, '').toLowerCase() : null; }, y: function(s) { return strReplace(s, SPECIAL_HTML_CHARS, function (m) { return m === '&' ? '&' : m === '<' ? '<' : m === '>' ? '>' : m === '"' ? '"' : m === "'" ? ''' : /*m === '`'*/ '`'; // in hex: 60 }); }, ya: function(s) { return strReplace(s, AMP, '&'); }, // FOR DETAILS, refer to inHTMLData() // Reference: https://html.spec.whatwg.org/multipage/syntax.html#data-state yd: function (s) { return strReplace(s, LT, '<'); }, yc: function (s) { return strReplace(s, SPECIAL_COMMENT_CHARS, function(m){ return m === '\x00' ? '\uFFFD' : m === '--!' || m === '--' || m === '-' || m === ']' ? m + ' ' :/* : m === ']>' ? '] >' : m === '-->' ? '-- >' : m === '--!>' ? '--! >' : /-*!?>/.test(m) ? */ m.slice(0, -1) + ' >'; }); }, yavd: function (s) { return strReplace(s, QUOT, '"'); }, yavs: function (s) { return strReplace(s, SQUOT, '''); }, yavu: function (s) { return strReplace(s, SPECIAL_ATTR_VALUE_UNQUOTED_CHARS, function (m) { return m === '\t' ? ' ' // in hex: 09 : m === '\n' ? ' ' // in hex: 0A : m === '\x0B' ? '' // in hex: 0B for IE. IE<9 \v equals v, so use \x0B instead : m === '\f' ? '' // in hex: 0C : m === '\r' ? ' ' // in hex: 0D : m === ' ' ? ' ' // in hex: 20 : m === '=' ? '=' // in hex: 3D : m === '<' ? '<' : m === '>' ? '>' : m === '"' ? '"' : m === "'" ? ''' : m === '`' ? '`' : '\uFFFD'; }); }, yu: encodeURI, yuc: encodeURIComponent, yubl: function (s) { return URI_BLACKLIST_PROTOCOLS[x.yup(s)] ? 'x-' + s : s; }, yufull: function (s) { return x.yu(s).replace(URL_IPV6, function(m, p) { return '//[' + p + ']'; }); }, yublf: function (s) { return x.yubl(x.yufull(s)); }, yceu: function(s) { s = htmlDecode(s); return CSS_VALID_VALUE.test(s) ? s : ";-x:'" + cssBlacklist(s.replace(CSS_SINGLE_QUOTED_CHARS, cssEncode)) + "';-v:"; }, yced: function(s) { return cssBlacklist(htmlDecode(s).replace(CSS_DOUBLE_QUOTED_CHARS, cssEncode)); }, string2 = \'([^\n\r\f\\']|\\{nl}|\\[^\n\r\f0-9a-f]|\\[0-9a-f]{1,6}(\r\n|[ \n\r\t\f])?)*\' yces: function(s) { return cssBlacklist(htmlDecode(s).replace(CSS_SINGLE_QUOTED_CHARS, cssEncode)); }, yceuu: function(s) { return cssUrl(s).replace(CSS_UNQUOTED_URL, function (chr) { return chr === '\'' ? '\\27 ' : chr === '(' ? '%28' : /* chr === ')' ? */ '%29'; }); }, yceud: function(s) { return cssUrl(s); }, yceus: function(s) { return cssUrl(s).replace(SQUOT, '\\27 '); } }); }; var privFilters = exports._privFilters = exports._getPrivFilters(); function uriInAttr (s, yav, yu) { return privFilters.yubl(yav((yu || privFilters.yufull)(s))); } exports.inHTMLData = privFilters.yd; exports.inHTMLComment = privFilters.yc; exports.inSingleQuotedAttr = privFilters.yavs; exports.inDoubleQuotedAttr = privFilters.yavd; exports.inUnQuotedAttr = privFilters.yavu; exports.uriInSingleQuotedAttr = function (s) { return uriInAttr(s, privFilters.yavs); }; exports.uriInDoubleQuotedAttr = function (s) { return uriInAttr(s, privFilters.yavd); }; exports.uriInUnQuotedAttr = function (s) { return uriInAttr(s, privFilters.yavu); }; exports.uriInHTMLData = privFilters.yufull; exports.uriInHTMLComment = function (s) { return privFilters.yc(privFilters.yufull(s)); }; exports.uriPathInSingleQuotedAttr = function (s) { return uriInAttr(s, privFilters.yavs, privFilters.yu); }; exports.uriPathInDoubleQuotedAttr = function (s) { return uriInAttr(s, privFilters.yavd, privFilters.yu); }; exports.uriPathInUnQuotedAttr = function (s) { return uriInAttr(s, privFilters.yavu, privFilters.yu); }; exports.uriPathInHTMLData = privFilters.yu; exports.uriPathInHTMLComment = function (s) { return privFilters.yc(privFilters.yu(s)); }; exports.uriQueryInSingleQuotedAttr = exports.uriPathInSingleQuotedAttr; exports.uriQueryInDoubleQuotedAttr = exports.uriPathInDoubleQuotedAttr; exports.uriQueryInUnQuotedAttr = exports.uriPathInUnQuotedAttr; exports.uriQueryInHTMLData = exports.uriPathInHTMLData; exports.uriQueryInHTMLComment = exports.uriPathInHTMLComment; exports.uriComponentInSingleQuotedAttr = function (s) { return privFilters.yavs(privFilters.yuc(s)); }; exports.uriComponentInDoubleQuotedAttr = function (s) { return privFilters.yavd(privFilters.yuc(s)); }; exports.uriComponentInUnQuotedAttr = function (s) { return privFilters.yavu(privFilters.yuc(s)); }; exports.uriComponentInHTMLData = privFilters.yuc; exports.uriComponentInHTMLComment = function (s) { return privFilters.yc(privFilters.yuc(s)); }; exports.uriFragmentInSingleQuotedAttr = function (s) { return privFilters.yubl(privFilters.yavs(privFilters.yuc(s))); }; exports.uriFragmentInDoubleQuotedAttr = function (s) { return privFilters.yubl(privFilters.yavd(privFilters.yuc(s))); }; exports.uriFragmentInUnQuotedAttr = function (s) { return privFilters.yubl(privFilters.yavu(privFilters.yuc(s))); }; exports.uriFragmentInHTMLData = exports.uriComponentInHTMLData; exports.uriFragmentInHTMLComment = exports.uriComponentInHTMLComment; var utils = { each: function(stack, handler) { var len = stack.length; if (len) { for(var i = 0; i < len; i++) { if (handler.call(stack[i], stack[i], i) === false) break; } } else if (typeof len === 'undefined') { for(var name in stack) { if (handler.call(stack[name], stack[name], name) === false) break; } } }, str_trim: function(string) { return string.replace(/^\s+/g, '').replace(/\s+$/g, ''); }, arr_compact: function(array) { var result = []; utils.each(array, function(item) { if (utils.str_trim(item) != '') { result.push(item); } }); return result; }, isObject: function(obj) { return obj === Object(obj); }, extend: function(target, obj) { utils.each(obj, function(value, key) { target[key] = value; }); } }; module.exports = utils; var utils = require('./utils'); var REGEXP_TAG_STYLE = /